package com.hanxiaozhang.idgenerator.snowflake;

import java.text.SimpleDateFormat;
import java.util.Date;


/**
 * 功能描述: <br>
 * 〈雪花id〉
 * <p>
 * https://blog.csdn.net/m0_68615056/article/details/123999774
 *
 * @Author: hanxinghua
 * @Date: 2023/12/12
 */
public class IdWorker {


    public static void main(String[] args) {
        IdWorker idWorker = new IdWorker(1, 1, 1);
        long id = idWorker.nextId();
        System.out.println(id);
        System.out.println(toBinaryStr(id));

        System.out.println(System.currentTimeMillis());


    }


    /**
     * 第2部分(41位)
     * 开始时间截（项目开始/上线时间）
     */
    private long twepoch = 1702371344779L;

    /**
     * 第3部分（10位）
     * 工作机器id(0~31)
     */
    private long workerId;

    /**
     * 第3部分（10位）
     * 数据中心id(0~31)
     */
    private long dataCenterId;

    /**
     * 第4部分（12位）
     * 1毫秒内序号(0~4095)
     */
    private long sequence = 0L;

    /**
     * 机器id所占的位数
     */
    private long workerIdBits = 5L;

    /**
     * 数据中心id所占的位数
     */
    private long dataCenterIdBits = 5L;

    /**
     * 序列在id中占的位数
     */
    private long sequenceBits = 12L;

    /**
     * -1L ^ (-1L << 5) = 31
     * 支持的最大机器id，结果是31
     * <p>
     * 1L的二进制：
     * 0000000000000000000000000000000000000000000000000000000000000001
     * 1L反码：
     * 1111111111111111111111111111111111111111111111111111111111111110
     * 1L补码 =反码 + 1 ，即-1L的二进制
     * 1111111111111111111111111111111111111111111111111111111111111111
     * <p>
     * <p>
     * -1L << 5 ，向左移动5位，低位补0：
     * ·              11111111   11111111   11111111   11111111   // -1补码
     * ·      11111   11111111   11111111   11111111   11100000   //左移5位
     * ---------------------------------------------------------------------
     * ·              11111111   11111111   11111111   11100000   // 高位溢出舍弃
     * <p>
     * -1L ^ (-1L << 5) ：异或( ^ )  两个数相同，结果为0，不相同则为1：
     * ·              11111111   11111111   11111111   11111111   // -1补码
     * .       ^      11111111   11111111   11111111   11100000   //左移5位的结果
     * ---------------------------------------------------------------------
     * ·              00000000   00000000   00000000   00011111
     */
    private long maxWorkerId = -1L ^ (-1L << workerIdBits);

    /**
     * -1L ^ (-1L << 5) = 31
     * 支持的最大数据中心id，结果是31
     */
    private long maxDatacenterId = -1L ^ (-1L << dataCenterIdBits);

    /**
     * -1L ^ (-1L << 12) = 4095
     * 自增长最大值4095，0开始
     */
    private long sequenceMask = -1L ^ (-1L << sequenceBits);

    /**
     * 机器id向左移12位
     */
    private long workerIdShift = sequenceBits;

    /**
     * 数据中心id向左移17位(12+5)
     */
    private long dataCenterIdShift = sequenceBits + workerIdBits;

    /**
     * 时间截向左移22位(5+5+12)
     */
    private long timestampLeftShift = sequenceBits + workerIdBits + dataCenterIdBits;

    /**
     * 上次生成id的时间截
     */
    private long lastTimestamp = -1L;


    /**
     * 构造器
     *
     * @param workerId     机器id
     * @param dataCenterId 数据中心id
     * @param sequence     指定序号
     */
    public IdWorker(long workerId, long dataCenterId, long sequence) {
        this(workerId, dataCenterId);
        this.sequence = sequence;
    }


    /**
     * 构造器
     *
     * @param workerId     机器id
     * @param dataCenterId 数据中心id
     */
    public IdWorker(long workerId, long dataCenterId) {
        // sanity check for workerId
        if (workerId > maxWorkerId || workerId < 0) {
            throw new IllegalArgumentException(String.format("worker Id can't be greater than %d or less than 0", maxWorkerId));
        }
        if (dataCenterId > maxDatacenterId || dataCenterId < 0) {
            throw new IllegalArgumentException(String.format("datacenter Id can't be greater than %d or less than 0", maxDatacenterId));
        }

        System.out.printf("worker starting. timestamp left shift %d, datacenter id bits %d, worker id bits %d, sequence bits %d, workerid %d",
                timestampLeftShift, dataCenterIdBits, workerIdBits, sequenceBits, workerId);

        this.workerId = workerId;
        this.dataCenterId = dataCenterId;
    }


    /**
     * 获得下一个ID
     *
     * @return
     */
    public synchronized long nextId() {

        // 获取当前时间
        long timestamp = timeGen();

        // 如果当前时间小于上一次ID生成的时间戳，说明系统时钟回退过这个时候应当抛出异常
        if (timestamp < lastTimestamp) {
            System.err.printf("clock is moving backwards.  Rejecting requests until %d.", lastTimestamp);
            throw new RuntimeException(String.format("Clock moved backwards.  Refusing to generate id for %d milliseconds",
                    lastTimestamp - timestamp));
        }

        // 如果是同一时间生成的，则进行毫秒内序列
        if (lastTimestamp == timestamp) {

            // 与(&)位运算：两个数都为1，结果为1，否则为0
            //         ... ...   00010000   00000000   //4096
            //    &    ... ...   00001111   11111111   //4095
            //---------------------------------------------------------------------
            //         ... ...   00000000   00000000   //0

            // 保证sequence不会超过序列号所能容纳的最大值
            sequence = (sequence + 1) & sequenceMask;

            // 如果sequence==0，说明已经到了最大数量+1
            if (sequence == 0) {
                // 时间等待1毫秒
                timestamp = tilNextMillis(lastTimestamp);
            }
        } else {
            // 每一个毫秒从0开始生成
            sequence = 0;
        }

        // 记录上次生成序列的时间
        lastTimestamp = timestamp;

        // 构造最终的数据：时间戳 + 数据中心位 + 机器号位 + 序列
        return ((timestamp - twepoch) << timestampLeftShift) |
                (dataCenterId << dataCenterIdShift) |
                (workerId << workerIdShift) |
                sequence;
    }


    /**
     * 转换成二进制
     *
     * @param num
     * @return
     */
    public static String toBinaryStr(long num) {
        String binaryString = Long.toBinaryString(num);
        while (binaryString.length() < 64) {
            binaryString = "0" + binaryString;
        }
        return binaryString;
    }

    /**
     * 获取workerId
     *
     * @return
     */
    public long getWorkerId() {
        return workerId;
    }

    /**
     * 获取dataCenterId
     *
     * @return
     */
    public long getDataCenterId() {
        return dataCenterId;
    }


    /**
     * 阻塞到下一个毫秒，直到获得新的时间戳
     *
     * @param lastTimestamp 上次生成ID的时间截
     * @return 当前时间戳
     */
    private long tilNextMillis(long lastTimestamp) {
        long timestamp = timeGen();
        // 如果当前时间小于等于上次生成时间，目的是等待下一个毫秒
        while (timestamp <= lastTimestamp) {
            timestamp = timeGen();
        }
        return timestamp;
    }

    /**
     * 返回以毫秒为单位的当前时间
     *
     * @return
     */
    private long timeGen() {
        return System.currentTimeMillis();
    }

}
